package bma.common.netty.protobuf.core.impl;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import bma.common.langutil.ai.stack.AIStack;
import bma.common.langutil.ai.stack.AIStackROOT;
import bma.common.langutil.core.ClassUtil;
import bma.common.langutil.core.ExceptionUtil;
import bma.common.langutil.core.ValueUtil;
import bma.common.netty.protobuf.core.BaseProtos.PError;
import bma.common.netty.protobuf.core.BaseProtos.PRequest;
import bma.common.netty.protobuf.core.PRequestContext;
import bma.common.netty.protobuf.core.PService;
import bma.common.netty.protobuf.core.PServiceMethod;
import bma.common.netty.protobuf.core.PUtil;

import com.google.protobuf.GeneratedMessage;
import com.google.protobuf.Message;

public abstract class PServiceBase implements PService {

	final org.slf4j.Logger log = org.slf4j.LoggerFactory
			.getLogger(PServiceBase.class);

	public static class CallInfo {
		public Method method;
		public Class argsType;
		public Method builderFactory;
		public boolean aicall;
	}

	protected String target;
	protected Map<String, CallInfo> methods;
	protected static ThreadLocal<PRequestContext> context = new ThreadLocal<PRequestContext>();

	public static PRequestContext currentContext() {
		return context.get();
	}

	public PServiceBase() {
		super();
	}

	public PServiceBase(String target) {
		super();
		this.target = target;
	}

	public String getTarget() {
		return target;
	}

	public void setTarget(String target) {
		this.target = target;
	}

	@Override
	public boolean process(PRequestContext ctx) {
		return invokeCallInfo(ctx);
	}

	public boolean invokeCallInfo(PRequestContext ctx) {
		PRequest req = ctx.getRequest();
		String name = req.getName();
		CallInfo call = methods.get(name);
		if (call == null) {
			if (log.isDebugEnabled()) {
				log.debug("[{}] {}:{} not found", new Object[] {
						ctx.getHeader().getId(), req.getTarget(), name });
			}
			PError err = PUtil.error404("'" + target + "' method '" + name
					+ "' not found");
			ctx.response(err, null);
			return true;
		}

		try {
			AIStackROOT<GeneratedMessage> callStack = null;
			if (call.aicall) {
				final PRequestContext fctx = ctx;
				callStack = new AIStackROOT<GeneratedMessage>() {

					@Override
					public boolean end(GeneratedMessage result, Throwable t) {
						fctx.response(result, t);
						return true;
					}
				};
			}
			Object[] args;
			if (call.argsType != null) {
				Message.Builder b = (Message.Builder) call.builderFactory
						.invoke(null);
				Message o = PUtil.to(ctx.getInputStream(), call.argsType, b);
				if (!o.isInitialized()) {
					throw new IllegalArgumentException("["
							+ ctx.getHeader().getId() + "] " + req.getTarget()
							+ ":" + name + " argument invalid");
				}
				if (callStack != null) {
					args = new Object[] { callStack, o };
				} else {
					args = new Object[] { o };
				}
			} else {
				if (callStack != null) {
					args = new Object[] { callStack };
				} else {
					args = new Object[0];
				}
			}
			context.set(ctx);
			try {
				Object r = call.method.invoke(this, args);
				if (log.isDebugEnabled()) {
					log.debug("[{}] {}:{} invoke success - {}", new Object[] {
							ctx.getHeader().getId(), req.getTarget(), name,
							r == null ? "null" : r.getClass().getSimpleName() });
				}
				if (!call.aicall) {
					ctx.response(r, null);
				}
			} catch (InvocationTargetException e) {
				throw e.getTargetException();
			} finally {
				context.set(null);
			}
		} catch (Throwable err) {
			if (log.isDebugEnabled()) {
				log.debug(
						"[" + ctx.getHeader().getId() + "] " + req.getTarget()
								+ ":" + name + " invoke error", err);
			}
			ctx.response(null, err);
			return true;
		}

		return true;
	}

	public void createMethods() {
		methods = new HashMap<String, CallInfo>();
		Method[] method = getClass().getMethods();
		for (Method m : method) {
			boolean b = m.isAnnotationPresent(PServiceMethod.class);
			if (b) {
				PServiceMethod anno = (PServiceMethod) m
						.getAnnotation(PServiceMethod.class);
				String name = anno.value();
				if (ValueUtil.empty(name)) {
					name = m.getName();
				}
				CallInfo call = new CallInfo();
				call.method = m;
				Class[] ps = m.getParameterTypes();
				if (ps != null) {
					if (ps.length == 0) {
					} else if (ps.length == 1) {
						if (ClassUtil
								.isImplementInterface(ps[0], AIStack.class)) {
							call.aicall = true;
						} else {
							call.argsType = ps[0];
						}
					} else if (ps.length == 2) {
						if (ClassUtil
								.isImplementInterface(ps[0], AIStack.class)) {
							call.argsType = ps[1];
							call.aicall = true;
						} else {
							throw new IllegalArgumentException(m.getName()
									+ " not AICall method");
						}
					} else {
						throw new IllegalArgumentException(m.getName()
								+ " params error");
					}
				}
				if (call.argsType != null) {
					if (!ClassUtil.isExtendFrom(call.argsType,
							GeneratedMessage.class)) {
						throw new IllegalArgumentException(m.getName()
								+ " param must GeneratedMessage");
					}
					try {
						call.builderFactory = call.argsType
								.getMethod("newBuilder");
					} catch (Exception e) {
						throw ExceptionUtil.throwRuntime(e);
					}
				}
				methods.put(name, call);
			}
		}
	}
}
